package com.yanqu.road.server.manger.activity.operamask;

import com.yanqu.road.entity.activity.operamask.OperaMaskConfig;
import com.yanqu.road.entity.activity.operamask.OperaMaskGameData;
import com.yanqu.road.entity.activity.operamask.config.OperaMaskSceneConfig;
import com.yanqu.road.entity.activity.operamask.game.OperaMaskBlock;
import com.yanqu.road.entity.activity.operamask.game.OperaMaskSkill;
import com.yanqu.road.entity.activity.operamask.sanxiao.OperaMaskBoom;
import com.yanqu.road.entity.activity.operamask.sanxiao.OperaMaskEliminateParam;
import com.yanqu.road.entity.activity.operamask.sanxiao.OperaMaskEliminateResult;
import com.yanqu.road.entity.activity.operamask.sanxiao.OperaMaskEliminateTip;
import com.yanqu.road.entity.activity.operamask.sanxiao.OperaMaskRoundResult;
import com.yanqu.road.entity.activity.operamask.sanxiao.OperaMaskSkillEffect;
import com.yanqu.road.entity.activity.operamask.sanxiao.OperaMaskStageResult;
import com.yanqu.road.entity.enums.eOperaMaskBlockType;
import com.yanqu.road.entity.enums.eOperaMaskBoomType;
import com.yanqu.road.entity.enums.eOperaMaskSkillType;
import com.yanqu.road.server.protocol.GameErrorCode;
import com.yanqu.road.utils.RandomHelper;
import com.yanqu.road.utils.config.Config;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

public class OperaMaskEliminateLogic {

    private static Logger logger = LogManager.getLogger(OperaMaskEliminateLogic.class.getName());

    /**
     * 移动方块，消除方块
     */
    public static OperaMaskEliminateResult moveBlock(OperaMaskEliminateParam param, OperaMaskConfig config, boolean tryForceScene) {
        Random random = new Random();

        // 参数
        List<OperaMaskSkill> skillList = param.getGameData().getSkillList();
        List<List<OperaMaskBlock>> listList = param.getGameData().getBlockList();
        int fromX = param.getFromX();
        int fromY = param.getFromY();
        int toX = param.getToX();
        int toY = param.getToY();
        OperaMaskSceneConfig sceneConfig = OperaMaskMgr.getConfig().getSceneConfigMap().get(param.getGameData().getSceneId()); // 剧目配置
        boolean isFirstMove = param.isFirstMove();

        // 能否斜向
        boolean canObliqueMove = OperaMaskSkillLogic.getSkill04Effect(skillList, config) > 0;
        boolean canObliqueEliminate = OperaMaskSkillLogic.getSkill08Effect(skillList);

        // 校验移动参数
        int ret = checkMoveParam(listList, fromX, fromY, toX, toY, canObliqueMove);
        if (ret != 0) {
            return new OperaMaskEliminateResult(ret);
        }

        List<OperaMaskRoundResult> roundResultList = new ArrayList<>(); // 每轮结果
        List<OperaMaskBlock> lightEliminateList = new ArrayList<>(); // 消除方块发光
        List<OperaMaskSkillEffect> lightSkillList = new ArrayList<>(); // 技能发光
        Set<OperaMaskBlock> dropSet = new HashSet<>(); // 上一轮掉落的方块
        List<OperaMaskBlock> effect20Blocks = new ArrayList<>();// 技能20影响到的格子
        List<Integer> skill20Effect = OperaMaskSkillLogic.getSkill20Effect(param.getGameData().getSkillList(), config);

        // 交换格子位置
        exchangeBlock(listList, fromX, fromY, toX, toY, random, effect20Blocks, skill20Effect);

        // 记录交换格子的颜色
        Set<Integer> moveColorSet = new HashSet<>();
        moveColorSet.add(listList.get(fromY).get(fromX).getColor());
        moveColorSet.add(listList.get(toY).get(toX).getColor());
        moveColorSet.remove(Integer.valueOf(0));

        // 判断能否消除
        List<Integer> sceneEffect1006 = OperaMaskSkillLogic.getSceneSkillEffectList(sceneConfig, eOperaMaskSkillType.Skill1006);
        if (!canEliminate(listList, canObliqueEliminate, fromX, fromY, toX, toY)) {
            if (tryForceScene && sceneEffect1006 != null) {
                // 剧目效果
            } else {
                return new OperaMaskEliminateResult(GameErrorCode.E_OPERA_MASK_CAN_NOT_ELIMINATE);
            }
        }

        // 技能20 手动移动的处理一次
        handleSkill20(effect20Blocks, lightSkillList, skill20Effect);

        int skill17Amount = 0;
        // 【可以消除了】
        for (int round = 1; round < 100; round++) { // 假装是while

            Set<OperaMaskBlock> eliminateSet = new HashSet<>(); // 本轮消除的格子
            List<OperaMaskBoom> boomList = new ArrayList<>(); // 本轮爆炸信息
            Set<OperaMaskBlock> waitBoomSet = new HashSet<>(); // 下阶段爆炸的炸弹

            List<OperaMaskStageResult> stageResultList = new ArrayList<>(); // 阶段结果

            for (int stage = 1; stage < 100; stage++) {

                // 备份到上阶段为止的数据
                Set<OperaMaskBlock> lastEliminateSet = new HashSet<>(eliminateSet);
                List<OperaMaskBoom> lastBoomList = new ArrayList<>(boomList);

                // 1. 标记消除方块
                markEliminate(round, stage, canObliqueEliminate,
                        listList, eliminateSet, boomList, waitBoomSet,
                        fromX, fromY, toX, toY, random);

                // 2. 消除技能生效
                if (round == 1 && stage == 1) {

                    // 记录发光方块
                    lightEliminateList = new ArrayList<>(eliminateSet);

                    // 技能14
                    int skill14Effect = OperaMaskSkillLogic.getSkill14Effect(skillList, config);
                    if (skill14Effect > 0) {
                        handleSkill14(listList, eliminateSet, lightSkillList, boomList, waitBoomSet, canObliqueEliminate, skill14Effect,
                                random, effect20Blocks, skill20Effect);
                    }

                    //技能20 有改位置的技能处理一次
                    handleSkill20(effect20Blocks, lightSkillList, skill20Effect);

                    // 技能21
                    int skill21Effect = OperaMaskSkillLogic.getSkill21Effect(skillList, config);
                    if (skill21Effect > 0 && tryForceScene == true) {
                        handleSkill21(listList, eliminateSet, lightSkillList, boomList, waitBoomSet, canObliqueEliminate, skill21Effect);
                    }

                    // 技能04
                    int skill04Effect = OperaMaskSkillLogic.getSkill04Effect(skillList, config);
                    if (skill04Effect > 0) {
                        handleSkill04(listList, eliminateSet, lightSkillList, boomList, waitBoomSet, random, skill04Effect, fromX, fromY, toX, toY);
                    }

                    // 技能15
                    List<Integer> skill15Effect = OperaMaskSkillLogic.getSkill15Effect(skillList, config);
                    if (skill15Effect.get(0) > 0) {
                        handleSkill15(listList, eliminateSet, lightSkillList, boomList, waitBoomSet, random, skill15Effect);
                    }

                    // 技能01
                    int skill01Effect = OperaMaskSkillLogic.getSkill01Effect(skillList, config);
                    if (skill01Effect > 0) {
                        handleSkill01(listList, eliminateSet, lightSkillList, skill01Effect, canObliqueEliminate, boomList, waitBoomSet, fromX, fromY, toX, toY);
                    }

                    // 技能02
                    List<Integer> skill02Effect = OperaMaskSkillLogic.getSkill02Effect(skillList, config);
                    if (skill02Effect.get(0) > 0) {
                        handleSkill02(listList, eliminateSet, lightSkillList, skill02Effect, boomList, waitBoomSet, random);
                    }

                    // 技能13
                    int skill13Effect = OperaMaskSkillLogic.getSkill13Effect(skillList, config);
                    if (skill13Effect > 0) {
                        handleSkill13(random, listList, eliminateSet, boomList, waitBoomSet, lightSkillList, skill13Effect);
                    }
                }

                // 记录新增的数据为阶段结果
                OperaMaskStageResult stageResult = new OperaMaskStageResult();
                stageResult.setBlockList(eliminateSet.stream().filter(item -> !lastEliminateSet.contains(item)).collect(Collectors.toList()));
                stageResult.setBoomList(boomList.stream().skip(lastBoomList.size()).collect(Collectors.toList()));
                stageResultList.add(stageResult);

                // 彩虹炸弹特殊处理：
                boolean colorAndColor = stageResult.getBoomList().stream().anyMatch(item -> item.getType() == eOperaMaskBoomType.ColorAndColor.getValue());
                if (!colorAndColor) {
                    List<OperaMaskBlock> tmpBlock = stageResult.getBlockList();
                    List<OperaMaskBoom> tmpBoom = stageResult.getBoomList();
                    for (int i = 0; i < tmpBlock.size(); i++) {
                        OperaMaskBlock block = tmpBlock.get(i);
                        if (block.getType() == eOperaMaskBlockType.BombColorful.getValue()
                                || block.getType() == eOperaMaskBlockType.BombSuperColorful.getValue()) {
                            boolean noneMatch = tmpBoom.stream().noneMatch(item -> item.getX() == block.getX() && item.getY() == block.getY());
                            if (noneMatch) {
                                tmpBlock.remove(i--);
                                eliminateSet.remove(block);
                            }
                        }
                    }
                }

                if (waitBoomSet.isEmpty()) {
                    break; // 没有爆炸的话，就没有下一阶段了
                }
            }

            // 2.1 记录消除格子
            Set<OperaMaskBlock> roundEliminateSet = new HashSet<>(); // 本轮消除的格子
            for (OperaMaskStageResult stageResult : stageResultList) {
                roundEliminateSet.addAll(stageResult.getBlockList());
            }

            List<OperaMaskSkillEffect> roundSkillEffectListBeforeDrop = new ArrayList<>();

            // 2.2 生成自爆炸弹
            List<OperaMaskBlock> nextSelfBoomList = new ArrayList<>();
            OperaMaskSkillEffect effect17 = new OperaMaskSkillEffect(eOperaMaskSkillType.Skill17.getValue());
            boolean skill17Effect = OperaMaskSkillLogic.getSkill17Effect(skillList, config, skill17Amount);
            for (OperaMaskBlock block : roundEliminateSet) {
                if (block.getSelfBoom() != 0) {
                    continue;
                }
                if (block.getType() == eOperaMaskBlockType.BombSuperColorful.getValue()) {
                    OperaMaskBlock clone = block.cloneNoMulti();
                    clone.setSelfBoom(1);
                    nextSelfBoomList.add(clone);
                } else if (isSkill17Block(listList, canObliqueEliminate, skill17Effect, block, round, fromX, fromY, toX, toY)) {
                    OperaMaskBlock clone = block.cloneNoMulti();
                    clone.setSelfBoom(1);
                    nextSelfBoomList.add(clone);
                    effect17.getBlockList().add(clone);
                    skill17Amount++;
                    if (Config.isDebug()) {
                        logger.info("----------技能17 触发{}次 ", skill17Amount);
                    }
                }
            }
            if (!effect17.getBlockList().isEmpty()) {
                roundSkillEffectListBeforeDrop.add(effect17);
            }

            // 3. 消除
            setEliminateEmpty(listList, roundEliminateSet);

            // 4. 生成炸弹（正常生成 + 超级彩虹炸弹爆炸后生成）
            List<OperaMaskBlock> createBombList = OperaMaskCreateBombLogic.getCreateBombList(skillList,
                    round, fromX, fromY, toX, toY,
                    roundEliminateSet, dropSet, random, config, roundSkillEffectListBeforeDrop, param.getGameData().getSceneId());
            createBombList.addAll(nextSelfBoomList);

            // 炸弹添加到盘面
            for (OperaMaskBlock block : createBombList) {
                listList.get(block.getY()).set(block.getX(), block);
            }
            // 表现上移除技能生成的炸弹
            if (roundSkillEffectListBeforeDrop.size() > 0) {
                Set<OperaMaskBlock> skillBombSet = new HashSet<>();
                for (OperaMaskSkillEffect effect : roundSkillEffectListBeforeDrop) {
                    if (effect.getSkillId() == eOperaMaskSkillType.Skill99.getValue()) {
                        skillBombSet.addAll(effect.getBlockList());
                    }
                    if (effect.getSkillId() == eOperaMaskSkillType.Skill17.getValue()) {
                        skillBombSet.addAll(effect.getBlockList());
                    }
                }
                createBombList.removeAll(skillBombSet);
            }

            // 4.5 再替换一下炸弹
            if (round == 1) {
                OperaMaskSkillEffect effect16 = new OperaMaskSkillEffect(eOperaMaskSkillType.Skill16.getValue());
                int skill16Effect = OperaMaskSkillLogic.getSkill16Effect(skillList, config);
                if (skill16Effect > 0) {
                    for (int y = 0; y < listList.size(); y++) {
                        for (int x = 0; x < listList.get(y).size(); x++) {
                            OperaMaskBlock block = listList.get(y).get(x);
                            if (block != null && block.getType() == eOperaMaskBlockType.NormalBlock.getValue() && moveColorSet.contains(block.getColor())
                                    && random.nextInt(1000) < skill16Effect) {
                                block.setType(random.nextInt(1000) < 500 ? eOperaMaskBlockType.BombHeng.getValue() : eOperaMaskBlockType.BombShu.getValue());
                                effect16.getBlockList().add(block);
                            }
                        }
                    }
                }
                if (!effect16.getBlockList().isEmpty()) {
                    roundSkillEffectListBeforeDrop.add(effect16);
                }
            }

            // 累计消除方块数
            int eliminateBlockAmount = param.getGameData().getEliminateBlockAmount() + eliminateSet.size();
            param.getGameData().setEliminateBlockAmount(eliminateBlockAmount);
            if (Config.isDebug()) {
                logger.info("-----消除累计{}-------", eliminateBlockAmount);
            }

            // 5. 掉落方块
            dropSet = dropBlock(listList, random, sceneConfig, isFirstMove, param.getGameData(), effect20Blocks, skill20Effect);
            isFirstMove = false;

            List<List<OperaMaskBlock>> roundEndBlockList = OperaMaskMgr.cloneBlock(listList); // 备份下盘面

            // 6. 替换炸弹技能生效
            List<OperaMaskSkillEffect> roundSkillEffectList = new ArrayList<>();
            if (OperaMaskSkillLogic.getSkill03Effect(skillList)) {
                handleSkill03(listList, roundSkillEffectList);
            }
            if (OperaMaskSkillLogic.getSkill07Effect(skillList)) {
                handleSkill07(listList, roundSkillEffectList);
            }
            List<Integer> skill19Effect = OperaMaskSkillLogic.getSkill19Effect(skillList, config);
            if (skill19Effect != null) {
                handleSkill19(listList, roundSkillEffectList, skill19Effect, param.getGameData());
            }

            // 技能20 位置改变加倍 掉落后的处理一次
            handleSkill20(effect20Blocks, roundSkillEffectList, skill20Effect);

            // 7. 设置结果
            OperaMaskRoundResult roundResult = new OperaMaskRoundResult();
            roundResult.setStageResultList(stageResultList);
            roundResult.setCreateBomb(createBombList);
            roundResult.setListList(roundEndBlockList);
            roundResult.setSkillEffectList(roundSkillEffectList);
            roundResult.setSkillEffectListBeforeDrop(roundSkillEffectListBeforeDrop);
            roundResultList.add(roundResult);

            // 8. 判断重排
            checkRearrange(listList, canObliqueMove, canObliqueEliminate, roundResultList);

            if (roundEliminateSet.isEmpty()) {
                // 本轮没有消除，结束了
                break;
            }
        }

        List<OperaMaskSkillEffect> endEffectList = new ArrayList<>();

        // 9. 结束后技能
        int skill09Effect = OperaMaskSkillLogic.getSkill09Effect(skillList, config);
        if (skill09Effect > 0) {
            handleSkill09(listList, skillList, endEffectList, skill09Effect, random, config, param.getGameData());
        }

        // 设置结果
        OperaMaskEliminateResult result = new OperaMaskEliminateResult();
        result.setLightEliminateList(lightEliminateList);
        result.setLightSkillList(lightSkillList);
        result.setRoundResultList(roundResultList);
        result.setListList(OperaMaskMgr.cloneBlock(listList));
        result.addEndEffectList(endEffectList);
        return result;
    }

    private static boolean isSkill17Block(List<List<OperaMaskBlock>> listList, boolean canObliqueEliminate, boolean skill17Effect, OperaMaskBlock block,
                                          int round, int fromX, int fromY, int toX, int toY) {
        // 没技能
        if (!skill17Effect) {
            return false;
        }
        // 不是炸弹
        if (!(block.getType() == eOperaMaskBlockType.BombShiZi.getValue()
                || block.getType() == eOperaMaskBlockType.BombHeng.getValue()
                || block.getType() == eOperaMaskBlockType.BombShu.getValue())) {
            return false;
        }
        // 不是移动引爆
        if (round == 1) {
            OperaMaskBlock from = listList.get(fromY).get(fromX);
            OperaMaskBlock to = listList.get(toY).get(toX);

            boolean fromIsColorful = from.getType() == eOperaMaskBlockType.BombColorful.getValue() || from.getType() == eOperaMaskBlockType.BombSuperColorful.getValue();
            boolean toIsColorful = to.getType() == eOperaMaskBlockType.BombColorful.getValue() || to.getType() == eOperaMaskBlockType.BombSuperColorful.getValue();

            // 彩虹+彩虹：炸弹都触发
            if (fromIsColorful && toIsColorful) {
                return true;
            }
            // 移动的：不触发
            if (block == from || block == to) {
                return false;
            }
            // 彩虹+非彩虹：跟他颜色一样的都不触发
            if (fromIsColorful && !toIsColorful && isBomb(to)
                    || toIsColorful && !fromIsColorful && isBomb(from)) {
                if (block.getColor() == from.getColor() || block.getColor() == to.getColor()) {
                    return false;
                }
            }
            // 正常三消：无法触发
            if (blockCanEliminate(listList, canObliqueEliminate, block)) {
                return false;
            }
        }
        return true;
    }

    /**
     * 掉落方块
     */
    private static Set<OperaMaskBlock> dropBlock(List<List<OperaMaskBlock>> listList, Random random, OperaMaskSceneConfig sceneConfig, boolean isFirstMove,
                                                 OperaMaskGameData gameData, List<OperaMaskBlock> effect20Blocks, List<Integer> skill20Effect) {
        Set<OperaMaskBlock> dropSet = new HashSet<>();
        // 旧方块掉落
        for (int y = 0; y < listList.size(); y++) {
            for (int x = 0; x < listList.get(y).size(); x++) {
                if (listList.get(y).get(x) == null) {
                    // 找他上面的格子掉落
                    for (int upY = y + 1; upY < listList.size(); upY++) {
                        if (listList.get(upY).get(x) != null) {
                            OperaMaskBlock up = listList.get(upY).get(x).clone(); // 复制一个出来
                            up.setY(y);
                            listList.get(y).set(x, up);
                            listList.get(upY).set(x, null);
                            dropSet.add(up); // 记录掉落格子

                            if (isEffect20(random, skill20Effect)) {
                                effect20Blocks.add(up);
                            }
                            break;
                        }
                    }
                }
            }
        }

        // 准备权重
        int colorNum = OperaMaskMgr.getConfig().getColorNum();
        List<Integer> weightList = new ArrayList<>();
        for (int i = 0; i < colorNum; i++) {
            weightList.add(1000);
        }
        List<Integer> sceneEffect1002 = OperaMaskSkillLogic.getSceneSkillEffectList(sceneConfig, eOperaMaskSkillType.Skill1002);
        if (sceneEffect1002 != null) {
            weightList.set(OperaMaskMgr.RED_COLOR - 1, weightList.get(OperaMaskMgr.RED_COLOR - 1) + sceneEffect1002.get(0));
        }
        List<Integer> sceneEffect1004 = OperaMaskSkillLogic.getSceneSkillEffectList(sceneConfig, eOperaMaskSkillType.Skill1004);
        if (sceneEffect1004 != null) {
            int color = sceneEffect1004.get(0);
            weightList.set(color - 1, 0);
        }

        // 掉落彩虹
        List<Integer> sceneEffect1003 = OperaMaskSkillLogic.getSceneSkillEffectList(sceneConfig, eOperaMaskSkillType.Skill1003);

        // 掉落新方块
        int eliminateBlockAmount = gameData.getEliminateBlockAmount();
        int guideIndex = OperaMaskMgr.BLOCK_LIST_WIDTH * OperaMaskMgr.BLOCK_LIST_HEIGHT; // 新手引导下标，由于只有一次就简单处理了
        for (int y = 0; y < listList.size(); y++) {
            for (int x = 0; x < listList.get(y).size(); x++) {
                if (listList.get(y).get(x) == null) {

                    OperaMaskBlock block;
                    if (isFirstMove && guideIndex < sceneConfig.getInitBlockList().length) {
                        // 新手引导最优先
                        block = new OperaMaskBlock(x, y, sceneConfig.getInitBlockList()[guideIndex++], eOperaMaskBlockType.NormalBlock.getValue());
                    } else if (sceneEffect1003 != null && 0 < sceneEffect1003.get(0) &&
                            0 < eliminateBlockAmount &&
                            eliminateBlockAmount >= sceneEffect1003.get(0)) {
                        // 彩虹炸弹技能生效  消除60个并下落，第61个下落的必定是彩虹炸弹
                        block = new OperaMaskBlock(x, y, 0, eOperaMaskBlockType.BombColorful.getValue());
                        if (Config.isDebug()) {
                            logger.info("-----生成彩虹炸弹-------{}", eliminateBlockAmount);
                        }
                        eliminateBlockAmount -= sceneEffect1003.get(0);
                    } else {
                        // 正常随机
                        block = new OperaMaskBlock(x, y, RandomHelper.getRandomIndexByWeight(weightList) + 1, eOperaMaskBlockType.NormalBlock.getValue());
                    }

                    listList.get(y).set(x, block);
                    dropSet.add(block); // 记录掉落格子
                }
            }
        }
        gameData.setEliminateBlockAmount(eliminateBlockAmount);

        return dropSet;
    }

    /**
     * 把消除的方块设置为null
     */
    private static void setEliminateEmpty(List<List<OperaMaskBlock>> listList, Set<OperaMaskBlock> eliminateSet) {
        for (OperaMaskBlock block : eliminateSet) {
            listList.get(block.getY()).set(block.getX(), null);
        }
    }

    /**
     * 标记消除方块
     */
    private static void markEliminate(int round, int stage, boolean canObliqueEliminate,
                                      List<List<OperaMaskBlock>> listList,
                                      Set<OperaMaskBlock> eliminateSet,
                                      List<OperaMaskBoom> boomList,
                                      Set<OperaMaskBlock> waitBoomSet,
                                      int fromX, int fromY, int toX, int toY, Random random) {

        Set<OperaMaskBlock> nextBoomSet = new HashSet<>(); // 组合炸弹引爆的其他炸弹需要下个阶段爆炸
        if (round == 1 && stage == 1) {
            // 移动后组合炸弹爆炸
            handleCombBoom(listList, eliminateSet, boomList, nextBoomSet, fromX, fromY, toX, toY, random);
        }

        boolean haveColorAndColor = boomList.stream().anyMatch(item -> item.getType() == eOperaMaskBoomType.ColorAndColor.getValue());
        if (haveColorAndColor) {
            // 有超级彩虹
            waitBoomSet.clear();
            nextBoomSet.clear();
        } else {
            if (stage == 1) {
                // 正常消除
                handleNormalEliminate(listList, eliminateSet, boomList, waitBoomSet, canObliqueEliminate);
            }
            // 炸弹连续爆炸
            handleWaitBombBoom(listList, eliminateSet, boomList, waitBoomSet);
            addWaitBoomList(boomList, waitBoomSet, new ArrayList<>(nextBoomSet));
        }
    }


    /**
     * 获取单独爆炸的类型
     */
    private static int getSingleBoomType(int blockType) {
        if (blockType == eOperaMaskBlockType.BombHeng.getValue()) {
            return eOperaMaskBoomType.Heng1.getValue();
        } else if (blockType == eOperaMaskBlockType.BombShu.getValue()) {
            return eOperaMaskBoomType.Shu1.getValue();
        } else if (blockType == eOperaMaskBlockType.BombShiZi.getValue()) {
            return eOperaMaskBoomType.ShiZi.getValue();
        } else if (blockType == eOperaMaskBlockType.BombColorful.getValue()
                || blockType == eOperaMaskBlockType.BombSuperColorful.getValue()) {
            return eOperaMaskBoomType.ColorSingle.getValue();
        }
        return -1;
    }

    /**
     * 炸弹连续爆炸（处理完后waitBoomSet会替换成后续要爆炸的格子）
     */
    private static void handleWaitBombBoom(List<List<OperaMaskBlock>> listList,
                                           Set<OperaMaskBlock> eliminateSet,
                                           List<OperaMaskBoom> boomList,
                                           Set<OperaMaskBlock> waitBoomSet) {

        Set<OperaMaskBlock> nextBoomSet = new HashSet<>();

        for (OperaMaskBlock bomb : waitBoomSet) {
            int boomType = getSingleBoomType(bomb.getType());
            if (boomType != -1) {
                // 爆炸
                OperaMaskBoom boom = new OperaMaskBoom(bomb.getX(), bomb.getY(), boomType);
                addBoomBlock(listList, boom, null);
                // 添加爆炸效果
                boomList.add(boom);
                // 添加消除的格子
                List<OperaMaskBlock> newEliminateList = boom.getBlockList().stream().filter(item -> !eliminateSet.contains(item)).collect(Collectors.toList());
                eliminateSet.addAll(newEliminateList);
                // 添加待爆炸格子
                addWaitBoomList(boomList, nextBoomSet, newEliminateList);
            }
        }

        // 替换下待爆炸炸弹
        waitBoomSet.clear();
        waitBoomSet.addAll(nextBoomSet);
    }

    /**
     * 添加待爆炸格子
     */
    private static void addWaitBoomList(List<OperaMaskBoom> boomList, Set<OperaMaskBlock> waitBoomSet, List<OperaMaskBlock> eliminateList) {
        for (OperaMaskBlock block : eliminateList) {
            boolean canBoom = isBomb(block) && !boomList.stream().anyMatch(item -> item.getX() == block.getX() && item.getY() == block.getY());
            if (canBoom) {
                waitBoomSet.add(block);
            }
        }
    }

    /**
     * 处理正常消除
     */
    private static void handleNormalEliminate(List<List<OperaMaskBlock>> listList,
                                              Set<OperaMaskBlock> eliminateSet,
                                              List<OperaMaskBoom> boomList,
                                              Set<OperaMaskBlock> waitBoomSet,
                                              boolean canObliqueEliminate) {

        // 看看哪些方块可以消除
        List<OperaMaskBlock> eliminateList = new ArrayList<>();
        for (List<OperaMaskBlock> list : listList) {
            for (OperaMaskBlock block : list) {
                if (eliminateSet.contains(block)) {
                    continue; // 已消除的跳过，避免重复爆炸
                }

                if (block.getSelfBoom() == 1) {
                    block.setSelfBoom(2);
                } else if (!blockCanEliminate(listList, canObliqueEliminate, block)) {
                    continue;
                }

                // 添加
                eliminateList.add(block);
            }
        }

        // 添加消除
        eliminateSet.addAll(eliminateList);
        // 添加待爆炸格子
        addWaitBoomList(boomList, waitBoomSet, eliminateList);
    }

    /**
     * 判断单个点能否消除
     */
    private static boolean blockCanEliminate(List<List<OperaMaskBlock>> listList, boolean canObliqueEliminate, OperaMaskBlock block) {

        int x = block.getX();
        int y = block.getY();
        int color = block.getColor();

        // 连续同色的数量
        int num = 0;

        // 1. 左到右
        for (int tmpX = x - 2; tmpX <= x + 2; tmpX++) {
            if (xyExist(listList, tmpX, y) && color == listList.get(y).get(tmpX).getColor()) {
                num++;
            } else {
                num = 0;
            }
            if (num >= 3) {
                return true;
            }
        }

        // 2. 下到上
        for (int tmpY = y - 2; tmpY <= y + 2; tmpY++) {
            if (xyExist(listList, x, tmpY) && color == listList.get(tmpY).get(x).getColor()) {
                num++;
            } else {
                num = 0;
            }
            if (num >= 3) {
                return true;
            }
        }

        if (canObliqueEliminate) {

            // 3. 左下到右上
            for (int tmpX = x - 2, tmpY = y - 2; tmpX <= x + 2 && tmpY <= y + 2; tmpX++, tmpY++) {
                if (xyExist(listList, tmpX, tmpY) && color == listList.get(tmpY).get(tmpX).getColor()) {
                    num++;
                } else {
                    num = 0;
                }
                if (num >= 3) {
                    return true;
                }
            }

            // 4. 右下到左上
            for (int tmpX = x + 2, tmpY = y - 2; tmpX >= x - 2 && tmpY <= y + 2; tmpX--, tmpY++) {
                if (xyExist(listList, tmpX, tmpY) && color == listList.get(tmpY).get(tmpX).getColor()) {
                    num++;
                } else {
                    num = 0;
                }
                if (num >= 3) {
                    return true;
                }
            }

        }

        return false;
    }

    /**
     * 处理组合炸弹
     */
    private static void handleCombBoom(List<List<OperaMaskBlock>> listList,
                                       Set<OperaMaskBlock> eliminateSet,
                                       List<OperaMaskBoom> boomList,
                                       Set<OperaMaskBlock> nextBoomSet,
                                       int fromX, int fromY, int toX, int toY,
                                       Random random) {

        OperaMaskBlock from = listList.get(fromY).get(fromX);
        OperaMaskBlock to = listList.get(toY).get(toX);

        if (from == to) {
            // 协议25914检查斜消时会用到，避免原地爆炸
            return;
        }

        boolean fromColorful = from.getType() == eOperaMaskBlockType.BombColorful.getValue()
                || from.getType() == eOperaMaskBlockType.BombSuperColorful.getValue();
        boolean toColorful = to.getType() == eOperaMaskBlockType.BombColorful.getValue()
                || to.getType() == eOperaMaskBlockType.BombSuperColorful.getValue();

        OperaMaskBoom boom = null;

        if (fromColorful && toColorful) {
            // 【彩虹 + 彩虹】
            boom = new OperaMaskBoom(toX, toY, eOperaMaskBoomType.ColorAndColor.getValue());
            addBoomBlock(listList, boom, from);


        } else if (fromColorful && isBomb(to) || toColorful && isBomb(from)) {
            // 【彩虹 + 炸弹】
            OperaMaskBlock otherBlock = fromColorful ? to : from;
            boom = new OperaMaskBoom(to.getX(), to.getY(),
                    isBomb(otherBlock) ? eOperaMaskBoomType.ColorAndBomb.getValue() : eOperaMaskBoomType.ColorAndNormal.getValue(),
                    otherBlock.getColor(), otherBlock.getType());
            addBoomBlock(listList, boom, from);


        } else if (fromColorful || toColorful) {
            // 【彩虹 + 普通】
            OperaMaskBlock colorfulBlock = fromColorful ? from : to;
            OperaMaskBlock otherBlock = fromColorful ? to : from;
            boom = new OperaMaskBoom(colorfulBlock.getX(), colorfulBlock.getY(),
                    isBomb(otherBlock) ? eOperaMaskBoomType.ColorAndBomb.getValue() : eOperaMaskBoomType.ColorAndNormal.getValue(),
                    otherBlock.getColor(), otherBlock.getType());
            addBoomBlock(listList, boom, from);


        } else if (isBomb(from) && isBomb(to)) {
            // 【炸弹 + 炸弹】
            eOperaMaskBoomType boomType = null;
            if (from.getType() == eOperaMaskBlockType.BombShiZi.getValue() && to.getType() == eOperaMaskBlockType.BombShiZi.getValue()) {
                boomType = eOperaMaskBoomType.Heng3Shu3;
            } else if (from.getType() == eOperaMaskBlockType.BombHeng.getValue() && to.getType() == eOperaMaskBlockType.BombHeng.getValue()
                    || from.getType() == eOperaMaskBlockType.BombShu.getValue() && to.getType() == eOperaMaskBlockType.BombShu.getValue()) {
                if (from.getX() != to.getX() && from.getY() != to.getY()) {
                    boomType = random.nextInt(1000) > 500 ? eOperaMaskBoomType.Heng3 : eOperaMaskBoomType.Shu3; // 斜向移动随机一种爆炸
                } else {
                    boomType = from.getX() == to.getX() ? eOperaMaskBoomType.Shu3 : eOperaMaskBoomType.Heng3; // 非斜向移动，看移动方向
                }
            } else if (from.getType() == eOperaMaskBlockType.BombHeng.getValue() && to.getType() == eOperaMaskBlockType.BombShu.getValue()
                    || from.getType() == eOperaMaskBlockType.BombShu.getValue() && to.getType() == eOperaMaskBlockType.BombHeng.getValue()) {
                boomType = eOperaMaskBoomType.ShiZi;
            } else if (from.getType() == eOperaMaskBlockType.BombShiZi.getValue() && to.getType() == eOperaMaskBlockType.BombHeng.getValue()
                    || from.getType() == eOperaMaskBlockType.BombHeng.getValue() && to.getType() == eOperaMaskBlockType.BombShiZi.getValue()) {
                boomType = eOperaMaskBoomType.Heng3Shu1;
            } else if (from.getType() == eOperaMaskBlockType.BombShiZi.getValue() && to.getType() == eOperaMaskBlockType.BombShu.getValue()
                    || from.getType() == eOperaMaskBlockType.BombShu.getValue() && to.getType() == eOperaMaskBlockType.BombShiZi.getValue()) {
                boomType = eOperaMaskBoomType.Heng1Shu3;
            } else {
                // 走到这有BUG
                return;
            }
            boom = new OperaMaskBoom(to.getX(), to.getY(), boomType.getValue());
            addBoomBlock(listList, boom, from);


        }

        // 没组合炸弹就返回了
        if (boom == null) {
            return;
        }

        // 添加爆炸效果（组合炸弹两个格子都要添加）
        boomList.add(boom);
        if (boom.getX() == fromX && boom.getY() == fromY) {
            boomList.add(new OperaMaskBoom(toX, toY, eOperaMaskBoomType.Empty.getValue()));
        } else {
            boomList.add(new OperaMaskBoom(fromX, fromY, eOperaMaskBoomType.Empty.getValue()));
        }

        // 添加炸弹消除格子
        eliminateSet.add(from);
        eliminateSet.add(to);
        eliminateSet.addAll(boom.getBlockList());
        // 添加待爆炸格子（全屏清时不需要其他炸弹爆炸了）
        if (boom.getType() != eOperaMaskBoomType.ColorAndColor.getValue()) {
            addWaitBoomList(boomList, nextBoomSet, boom.getBlockList());
        }
    }

    /**
     * 根据爆炸效果记录消除格子
     *
     * @param from 组合炸弹非停留点的格子，没有就是null
     */
    private static void addBoomBlock(List<List<OperaMaskBlock>> listList, OperaMaskBoom boom, OperaMaskBlock from) {
        Set<OperaMaskBlock> eliminateSet = new HashSet<>();

        if (boom.getType() == eOperaMaskBoomType.ColorAndColor.getValue()) {
            // 彩虹炸弹 + 彩虹炸弹 : 全屏消
            eliminateSet.add(listList.get(boom.getY()).get(boom.getX()));
            for (List<OperaMaskBlock> list : listList) {
                eliminateSet.addAll(list);
            }


        } else if (boom.getType() == eOperaMaskBoomType.ColorAndBomb.getValue()) {
            // 彩虹炸弹 + 其他炸弹 : 消除同色 && 替换为炸弹
            eliminateSet.add(listList.get(boom.getY()).get(boom.getX()));
            for (List<OperaMaskBlock> list : listList) {
                for (OperaMaskBlock block : list) {
                    if (block.getColor() == boom.getColorfulColor()) {
                        block.setType(boom.getColorfulType()); // 替换炸弹
                        eliminateSet.add(block);
                    }
                }
            }


        } else if (boom.getType() == eOperaMaskBoomType.ColorAndNormal.getValue()) {
            // 彩虹炸弹 + 普通格子 : 消除同色
            eliminateSet.add(listList.get(boom.getY()).get(boom.getX()));
            for (List<OperaMaskBlock> list : listList) {
                for (OperaMaskBlock block : list) {
                    if (block.getColor() == boom.getColorfulColor()) {
                        eliminateSet.add(block);
                    }
                }
            }


        } else if (boom.getType() == eOperaMaskBoomType.ColorSingle.getValue()) {
            // 彩虹炸弹 + 普通格子 : 消除同色

            // 随机一个颜色，且当前格子也要爆炸
            int colorNum = OperaMaskMgr.getConfig().getColorNum();
            boom.setColorfulColor(new Random().nextInt(colorNum) + 1);

            eliminateSet.add(listList.get(boom.getY()).get(boom.getX()));
            for (List<OperaMaskBlock> list : listList) {
                for (OperaMaskBlock block : list) {
                    if (block.getColor() == boom.getColorfulColor()) {
                        eliminateSet.add(block);
                    }
                }
            }


        } else if (boom.getType() == eOperaMaskBoomType.Heng1.getValue()) {
            for (int x = 0; x < listList.get(boom.getY()).size(); x++) {
                if (xyExist(listList, x, boom.getY())) eliminateSet.add(listList.get(boom.getY()).get(x));
            }


        } else if (boom.getType() == eOperaMaskBoomType.Shu1.getValue()) {
            for (int y = 0; y < listList.size(); y++) {
                if (xyExist(listList, boom.getX(), y)) eliminateSet.add(listList.get(y).get(boom.getX()));
            }


        } else if (boom.getType() == eOperaMaskBoomType.ShiZi.getValue()) {
            for (int x = 0; x < listList.get(boom.getY()).size(); x++) {
                if (xyExist(listList, x, boom.getY())) eliminateSet.add(listList.get(boom.getY()).get(x));
            }
            for (int y = 0; y < listList.size(); y++) {
                if (xyExist(listList, boom.getX(), y)) eliminateSet.add(listList.get(y).get(boom.getX()));
            }


        } else if (boom.getType() == eOperaMaskBoomType.Heng3.getValue()) {
            for (int y = boom.getY() - 1; y <= boom.getY() + 1; y++) {
                for (int x = 0; x < listList.get(0).size(); x++) {
                    if (xyExist(listList, x, y)) eliminateSet.add(listList.get(y).get(x));
                }
            }


        } else if (boom.getType() == eOperaMaskBoomType.Shu3.getValue()) {
            for (int x = boom.getX() - 1; x <= boom.getX() + 1; x++) {
                for (int y = 0; y < listList.size(); y++) {
                    if (xyExist(listList, x, y)) eliminateSet.add(listList.get(y).get(x));
                }
            }


        } else if (boom.getType() == eOperaMaskBoomType.Heng3Shu3.getValue()) {
            for (int y = boom.getY() - 1; y <= boom.getY() + 1; y++) {
                for (int x = 0; x < listList.get(0).size(); x++) {
                    if (xyExist(listList, x, y)) eliminateSet.add(listList.get(y).get(x));
                }
            }
            for (int x = boom.getX() - 1; x <= boom.getX() + 1; x++) {
                for (int y = 0; y < listList.size(); y++) {
                    if (xyExist(listList, x, y)) eliminateSet.add(listList.get(y).get(x));
                }
            }


        } else if (boom.getType() == eOperaMaskBoomType.Heng1Shu3.getValue()) {
            for (int x = 0; x < listList.get(boom.getY()).size(); x++) {
                if (xyExist(listList, x, boom.getY())) eliminateSet.add(listList.get(boom.getY()).get(x));
            }
            for (int x = boom.getX() - 1; x <= boom.getX() + 1; x++) {
                for (int y = 0; y < listList.size(); y++) {
                    if (xyExist(listList, x, y)) eliminateSet.add(listList.get(y).get(x));
                }
            }


        } else if (boom.getType() == eOperaMaskBoomType.Heng3Shu1.getValue()) {
            for (int y = boom.getY() - 1; y <= boom.getY() + 1; y++) {
                for (int x = 0; x < listList.get(0).size(); x++) {
                    if (xyExist(listList, x, y)) eliminateSet.add(listList.get(y).get(x));
                }
            }
            for (int y = 0; y < listList.size(); y++) {
                if (xyExist(listList, boom.getX(), y)) eliminateSet.add(listList.get(y).get(boom.getX()));
            }


        }


        // 彩虹炸弹交换普通炸弹，不能把原来炸弹位置也炸了
        if (boom.getType() == eOperaMaskBoomType.ColorAndBomb.getValue()) {
            eliminateSet.remove(from.getType() == boom.getColorfulType() ? from : listList.get(boom.getY()).get(boom.getX()));
        }

        // 添加炸弹效果
        boom.setBlockList(new ArrayList<>(eliminateSet));
    }

    /**
     * 是否炸弹
     */
    public static boolean isBomb(OperaMaskBlock block) {
        if (block.getType() != eOperaMaskBlockType.NormalBlock.getValue()) {
            return true;
        }
        return false;
    }

    /**
     * 检查移动参数
     */
    private static int checkMoveParam(List<List<OperaMaskBlock>> listList, int fromX, int fromY, int toX, int toY, boolean canObliqueMove) {
        if (fromX == 0 && fromY == 0 && toX == 0 && toY == 0) {
            return 0; // 检查斜消
        }
        int xDis = Math.abs(fromX - toX);
        int yDis = Math.abs(fromY - toY);
        if (!xyExist(listList, fromX, fromY) || // 坐标要存在
                !xyExist(listList, fromX, fromY) || // 坐标要存在
                xDis > 1 || yDis > 1 || // 同一方向距离不超过1
                !canObliqueMove && xDis + yDis > 1) { // 无法斜向移动时，只能有一个方向距离是1
            return GameErrorCode.E_OPERA_MASK_BLOCK_NO_ADJOIN;
        }
        return 0;
    }

    /**
     * 交换两个格子
     */
    private static void exchangeBlock(List<List<OperaMaskBlock>> blockListList, int x1, int y1, int x2, int y2,
                                      Random random, List<OperaMaskBlock> effect20Blocks, List<Integer> skill20Effect) {
        OperaMaskBlock block1 = blockListList.get(y1).get(x1);
        OperaMaskBlock block2 = blockListList.get(y2).get(x2);

        block1.setX(x2);
        block1.setY(y2);
        blockListList.get(y2).set(x2, block1);
        if (isEffect20(random, skill20Effect)) {
            effect20Blocks.add(block1);
        }


        block2.setX(x1);
        block2.setY(y1);
        blockListList.get(y1).set(x1, block2);
        if (block1.getType() == eOperaMaskBlockType.NormalBlock.getValue() || block2.getType() == eOperaMaskBlockType.NormalBlock.getValue()) {
            //两个都是炸弹类的,只算拖的格子移动
            if (isEffect20(random, skill20Effect)) {
                effect20Blocks.add(block2);
            }
        }
    }

    /**
     * 交换两个格子
     */
    private static void exchangeBlock(List<List<OperaMaskBlock>> blockListList, int x1, int y1, int x2, int y2) {
        OperaMaskBlock block1 = blockListList.get(y1).get(x1);
        OperaMaskBlock block2 = blockListList.get(y2).get(x2);

        block1.setX(x2);
        block1.setY(y2);
        blockListList.get(y2).set(x2, block1);

        block2.setX(x1);
        block2.setY(y1);
        blockListList.get(y1).set(x1, block2);
    }

    /**
     * 坐标是否存在
     */
    private static boolean xyExist(List<List<OperaMaskBlock>> blockListList, int x, int y) {
        if (x < 0 || y < 0) {
            return false;
        }
        if (blockListList.size() > y) {
            if (blockListList.get(y).size() > x) {
                return blockListList.get(y).get(x) != null;
            }
        }
        return false;
    }

    /**
     * 当前盘面能否消除
     */
    private static boolean canEliminate(List<List<OperaMaskBlock>> listList, boolean canObliqueEliminate,
                                        int fromX, int fromY, int toX, int toY) {
        OperaMaskBlock from = listList.get(fromY).get(fromX);
        OperaMaskBlock to = listList.get(toY).get(toX);

        if (from != to) {
            // 协议25914检查斜消时会用到，避免原地爆炸

            // 彩虹炸弹
            if (from.getType() == eOperaMaskBlockType.BombColorful.getValue() ||
                    to.getType() == eOperaMaskBlockType.BombColorful.getValue()) {
                return true;
            }

            // 超级彩虹炸弹
            if (from.getType() == eOperaMaskBlockType.BombSuperColorful.getValue() ||
                    to.getType() == eOperaMaskBlockType.BombSuperColorful.getValue()) {
                return true;
            }

            // 组合炸弹随意炸
            if (isBomb(from) && isBomb(to)) {
                return true;
            }
        }

        // 正常消除需要3个
        for (int y = 0; y < listList.size(); y++) {
            for (int x = 0; x < listList.get(y).size(); x++) {
                int color = listList.get(y).get(x).getColor();
                if (color == 0) {
                    continue;
                }
                // 右
                if (xyExist(listList, x + 1, y) && xyExist(listList, x + 2, y) &&
                        color == listList.get(y).get(x + 1).getColor() &&
                        color == listList.get(y).get(x + 2).getColor()) {
                    return true;
                }
                // 右上
                if (canObliqueEliminate &&
                        xyExist(listList, x + 1, y + 1) && xyExist(listList, x + 2, y + 2) &&
                        color == listList.get(y + 1).get(x + 1).getColor() &&
                        color == listList.get(y + 2).get(x + 2).getColor()) {
                    return true;
                }
                // 上
                if (xyExist(listList, x, y + 1) && xyExist(listList, x, y + 2) &&
                        color == listList.get(y + 1).get(x).getColor() &&
                        color == listList.get(y + 2).get(x).getColor()) {
                    return true;
                }
                // 左上
                if (canObliqueEliminate &&
                        xyExist(listList, x - 1, y + 1) && xyExist(listList, x - 2, y + 2) &&
                        color == listList.get(y + 1).get(x - 1).getColor() &&
                        color == listList.get(y + 2).get(x - 2).getColor()) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 检查重排
     */
    private static void checkRearrange(List<List<OperaMaskBlock>> listList, boolean canObliqueMove, boolean canObliqueEliminate,
                                       List<OperaMaskRoundResult> roundResultList) {

        // 没返回就是要重排了
        OperaMaskEliminateTip eliminateTips = getEliminateTip(listList, canObliqueMove, canObliqueEliminate);
        if (eliminateTips != null) {
            return;
        }

        // 打乱格子
        List<OperaMaskBlock> allList = new ArrayList<>();
        for (List<OperaMaskBlock> list : listList) {
            allList.addAll(list);
        }
        Collections.shuffle(allList);

        // 重排
        int index = 0;
        for (int y = 0; y < listList.size(); y++) {
            for (int x = 0; x < listList.get(y).size(); x++) {
                OperaMaskBlock block = allList.get(index++).clone();
                block.setX(x);
                block.setY(y);
                listList.get(y).set(x, block);
            }
        }

        // 添加重排结果
        OperaMaskRoundResult roundResult = new OperaMaskRoundResult();
        roundResult.setType(2);
        roundResult.setListList(listList);
        roundResultList.add(roundResult);
    }

    /**
     * 获取消除提示
     */
    public static OperaMaskEliminateTip getEliminateTip(List<List<OperaMaskBlock>> listList, boolean canObliqueMove, boolean canObliqueEliminate) {

        // 检查彩虹炸弹
        for (List<OperaMaskBlock> list : listList) {
            for (OperaMaskBlock block : list) {
                if (block.getType() == eOperaMaskBlockType.BombColorful.getValue()
                        || block.getType() == eOperaMaskBlockType.BombSuperColorful.getValue()) {
                    return new OperaMaskEliminateTip(block);
                }
            }
        }

        // 检查组合炸弹
        for (int y = 0; y < listList.size(); y++) {
            for (int x = 0; x < listList.get(y).size(); x++) {
                OperaMaskBlock from = listList.get(y).get(x);
                if (isBomb(from)) {
                    if (xyExist(listList, x + 1, y) && isBomb(listList.get(y).get(x + 1))) {
                        return new OperaMaskEliminateTip(from, listList.get(y).get(x + 1));
                    }
                    if (canObliqueMove && xyExist(listList, x + 1, y + 1) && isBomb(listList.get(y + 1).get(x + 1))) {
                        return new OperaMaskEliminateTip(from, listList.get(y + 1).get(x + 1));
                    }
                    if (xyExist(listList, x, y + 1) && isBomb(listList.get(y + 1).get(x))) {
                        return new OperaMaskEliminateTip(from, listList.get(y + 1).get(x));
                    }
                    if (canObliqueMove && xyExist(listList, x - 1, y + 1) && isBomb(listList.get(y + 1).get(x - 1))) {
                        return new OperaMaskEliminateTip(from, listList.get(y + 1).get(x - 1));
                    }
                }
            }
        }

        // 复制一份出来
        listList = OperaMaskMgr.cloneBlock(listList);

        // 遍历每个方块尝试交换
        for (int y = 0; y < listList.size(); y++) {
            for (int x = 0; x < listList.get(y).size(); x++) {

                int x2 = x + 1;
                int y2 = y;
                if (xyExist(listList, x2, y2)) {
                    exchangeBlock(listList, x, y, x2, y2);
                    if (canEliminate(listList, canObliqueEliminate, x, y, x2, y2)) {
                        Set<OperaMaskBlock> set = new HashSet<>();
                        handleNormalEliminate(listList, set, new ArrayList<>(), new HashSet<>(), canObliqueEliminate);
                        exchangeBlock(listList, x, y, x2, y2);
                        return new OperaMaskEliminateTip(listList.get(y).get(x), listList.get(y2).get(x2), set);
                    }
                    exchangeBlock(listList, x, y, x2, y2);
                }

                x2 = x + 1;
                y2 = y + 1;
                if (canObliqueMove && xyExist(listList, x2, y2)) {
                    exchangeBlock(listList, x, y, x2, y2);
                    if (canEliminate(listList, canObliqueEliminate, x, y, x2, y2)) {
                        Set<OperaMaskBlock> set = new HashSet<>();
                        handleNormalEliminate(listList, set, new ArrayList<>(), new HashSet<>(), canObliqueEliminate);
                        exchangeBlock(listList, x, y, x2, y2);
                        return new OperaMaskEliminateTip(listList.get(y).get(x), listList.get(y2).get(x2), set);
                    }
                    exchangeBlock(listList, x, y, x2, y2);
                }

                x2 = x;
                y2 = y + 1;
                if (xyExist(listList, x2, y2)) {
                    exchangeBlock(listList, x, y, x2, y2);
                    if (canEliminate(listList, canObliqueEliminate, x, y, x2, y2)) {
                        Set<OperaMaskBlock> set = new HashSet<>();
                        handleNormalEliminate(listList, set, new ArrayList<>(), new HashSet<>(), canObliqueEliminate);
                        exchangeBlock(listList, x, y, x2, y2);
                        return new OperaMaskEliminateTip(listList.get(y).get(x), listList.get(y2).get(x2), set);
                    }
                    exchangeBlock(listList, x, y, x2, y2);
                }

                x2 = x - 1;
                y2 = y + 1;
                if (canObliqueMove && xyExist(listList, x2, y2)) {
                    exchangeBlock(listList, x, y, x2, y2);
                    if (canEliminate(listList, canObliqueEliminate, x, y, x2, y2)) {
                        Set<OperaMaskBlock> set = new HashSet<>();
                        handleNormalEliminate(listList, set, new ArrayList<>(), new HashSet<>(), canObliqueEliminate);
                        exchangeBlock(listList, x, y, x2, y2);
                        return new OperaMaskEliminateTip(listList.get(y).get(x), listList.get(y2).get(x2), set);
                    }
                    exchangeBlock(listList, x, y, x2, y2);
                }

            }
        }

        return null;
    }

    // =================================================

    public static void handleSkill01(List<List<OperaMaskBlock>> listList,
                                     Set<OperaMaskBlock> eliminateSet,
                                     List<OperaMaskSkillEffect> effectList,
                                     int skill01Effect,
                                     boolean canObliqueEliminate,
                                     List<OperaMaskBoom> boomList,
                                     Set<OperaMaskBlock> waitBoomSet,
                                     int fromX, int fromY, int toX, int toY) {

        // 准备找同色油彩
        OperaMaskBlock from = listList.get(fromY).get(fromX);
        OperaMaskBlock to = listList.get(toY).get(toX);

        // 移动彩虹炸弹不触发本效果
        if (from.getType() == eOperaMaskBlockType.BombColorful.getValue()
                || from.getType() == eOperaMaskBlockType.BombSuperColorful.getValue()
                || to.getType() == eOperaMaskBlockType.BombColorful.getValue()
                || to.getType() == eOperaMaskBlockType.BombSuperColorful.getValue()) {
            return;
        }

        Set<Integer> colorSet = new HashSet<>();
        if (isBomb(from) && isBomb(to)) {
            // 组合炸弹两个颜色都要
            colorSet.add(from.getColor());
            colorSet.add(to.getColor());
        } else {
            // 正常三消可能有两个颜色
            if (eliminateSet.contains(from)) {
                if (blockCanEliminate(listList, canObliqueEliminate, from)) {
                    colorSet.add(from.getColor());
                }
            }
            if (eliminateSet.contains(to)) {
                if (blockCanEliminate(listList, canObliqueEliminate, to)) {
                    colorSet.add(to.getColor());
                }
            }
        }

        // 生效
        for (Integer color : colorSet) {

            // 拿同色未消除的
            List<OperaMaskBlock> tmpList = new ArrayList<>();
            for (List<OperaMaskBlock> list : listList) {
                for (OperaMaskBlock block : list) {
                    if (block.getColor() == color && !eliminateSet.contains(block)) {
                        tmpList.add(block);
                    }
                }
            }

            // 打乱
            Collections.shuffle(tmpList);

            // 取前N个消除
            List<OperaMaskBlock> list = tmpList.stream().limit(Math.min(tmpList.size(), skill01Effect)).collect(Collectors.toList());
            eliminateSet.addAll(list);

            // 消除后格子马上爆炸
            Set<OperaMaskBlock> quickBoomSet = new HashSet<>();
            for (OperaMaskBlock block : list) {
                boolean canBoom = isBomb(block) && !boomList.stream().anyMatch(item -> item.getX() == block.getX() && item.getY() == block.getY());
                if (canBoom) {
                    quickBoomSet.add(block);
                }
            }
            handleWaitBombBoom(listList, eliminateSet, boomList, quickBoomSet);
            waitBoomSet.addAll(quickBoomSet);

            // 技能效果
            OperaMaskSkillEffect skillEffect = new OperaMaskSkillEffect(eOperaMaskSkillType.Skill01.getValue());
            skillEffect.setBlockList(list);
            effectList.add(skillEffect);
        }

    }

    public static void handleSkill02(List<List<OperaMaskBlock>> listList,
                                     Set<OperaMaskBlock> eliminateSet,
                                     List<OperaMaskSkillEffect> effectList,
                                     List<Integer> skill02Effect,
                                     List<OperaMaskBoom> boomList,
                                     Set<OperaMaskBlock> waitBoomSet,
                                     Random random) {

        int times = 0;
        for (int i = 0; i < skill02Effect.get(0); i++) {
            if (random.nextInt(1000) < skill02Effect.get(1)) {
                times++;
            }
        }

        // 判断概率
        if (times <= 0) {
            return;
        }

        // 拿未消除的
        List<OperaMaskBlock> blockList = new ArrayList<>();
        for (List<OperaMaskBlock> list : listList) {
            for (OperaMaskBlock block : list) {
                if (block.getX() == 0 || block.getX() == list.size() - 1
                        || block.getY() == 0 || block.getY() == listList.size() - 1) {
                    continue; // 边缘一圈不要
                }
                if (!eliminateSet.contains(block)) {
                    blockList.add(block);
                }
            }
        }

        // 打乱
        Collections.shuffle(blockList);

        // 技能效果格子
        Set<OperaMaskBlock> triggerBlockSet = new HashSet<>(); // 十字中心格子
        Set<OperaMaskBlock> effectBlockSet = new HashSet<>(); // 十字的5个格子

        // 取前N个十字消除
        for (int i = 0; i < blockList.size() && i < times; i++) {
            OperaMaskBlock block = blockList.get(i);

            // 触发格子
            triggerBlockSet.add(block);

            // 十字范围格子
            int x = block.getX();
            int y = block.getY();
            if (xyExist(listList, x + 1, y)) {
                effectBlockSet.add(listList.get(y).get(x + 1));
            }
            if (xyExist(listList, x, y + 1)) {
                effectBlockSet.add(listList.get(y + 1).get(x));
            }
            if (xyExist(listList, x - 1, y)) {
                effectBlockSet.add(listList.get(y).get(x - 1));
            }
            if (xyExist(listList, x, y - 1)) {
                effectBlockSet.add(listList.get(y - 1).get(x));
            }
            effectBlockSet.add(block);

        }

        // 添加消除的格子
        eliminateSet.addAll(effectBlockSet);

        // 消除后格子马上爆炸
        Set<OperaMaskBlock> quickBoomSet = new HashSet<>();
        for (OperaMaskBlock block : effectBlockSet) {
            boolean canBoom = isBomb(block) && !boomList.stream().anyMatch(item -> item.getX() == block.getX() && item.getY() == block.getY());
            if (canBoom) {
                quickBoomSet.add(block);
            }
        }
        handleWaitBombBoom(listList, eliminateSet, boomList, quickBoomSet);
        waitBoomSet.addAll(quickBoomSet);

        // 添加技能效果
        if (effectBlockSet.size() > 0) {
            // 触发格子效果
            OperaMaskSkillEffect skillEffect = new OperaMaskSkillEffect(eOperaMaskSkillType.Skill02.getValue());
            skillEffect.setBlockList(new ArrayList<>(triggerBlockSet));
            effectList.add(skillEffect);
            // 十字范围格子效果
            OperaMaskSkillEffect skillEffect2 = new OperaMaskSkillEffect(eOperaMaskSkillType.Skill201.getValue());
            skillEffect2.setBlockList(new ArrayList<>(effectBlockSet));
            effectList.add(skillEffect2);
        }
    }

    public static void handleSkill03(List<List<OperaMaskBlock>> listList,
                                     List<OperaMaskSkillEffect> effectList) {
        List<OperaMaskBlock> effectBlockList = new ArrayList<>();
        for (List<OperaMaskBlock> list : listList) {
            for (OperaMaskBlock block : list) {
                if (block.getType() == eOperaMaskBlockType.BombHeng.getValue()
                        || block.getType() == eOperaMaskBlockType.BombShu.getValue()) {
                    // 替换炸弹需要复制一份出来替换
                    OperaMaskBlock clone = block.clone();
                    clone.setType(eOperaMaskBlockType.BombShiZi.getValue());
                    listList.get(clone.getY()).set(clone.getX(), clone);
                    effectBlockList.add(clone);
                }
            }

        }

        if (effectBlockList.size() > 0) {
            OperaMaskSkillEffect effect = new OperaMaskSkillEffect(eOperaMaskSkillType.Skill03.getValue());
            effect.setBlockList(effectBlockList);
            effectList.add(effect);
        }
    }

    private static void handleSkill04(List<List<OperaMaskBlock>> listList,
                                      Set<OperaMaskBlock> eliminateSet,
                                      List<OperaMaskSkillEffect> skillEffectList,
                                      List<OperaMaskBoom> boomList,
                                      Set<OperaMaskBlock> waitBoomSet,
                                      Random random,
                                      int skill04Effect,
                                      int fromX, int fromY, int toX, int toY) {
        if (fromX == toX && fromY == toY) {
            return; // 协议25914检查斜消时会用到，避免原地爆炸
        }
        if (random.nextInt(1000) > skill04Effect) {
            return; // 没触发
        }

        List<OperaMaskBlock> effectBlockList = new ArrayList<>();
        if (fromX == toX) {
            // 竖线
            for (int y = 0; y < listList.size(); y++) {
                effectBlockList.add(listList.get(y).get(fromX));
            }
        } else if (fromY == toY) {
            // 横线
            for (int x = 0; x < listList.get(fromY).size(); x++) {
                effectBlockList.add(listList.get(fromY).get(x));
            }
        } else {
            // 有斜率，一次函数
            int k = (fromY - toY) / (fromX - toX) > 0 ? 1 : -1; // 斜率，只会是1或-1
            int b = fromY - k * fromX;
            for (int y = 0; y < listList.size(); y++) {
                for (int x = 0; x < listList.get(y).size(); x++) {
                    if (y == k * x + b) {
                        effectBlockList.add(listList.get(y).get(x));
                    }
                }
            }
        }

        if (effectBlockList.size() > 0) {

            // 消除格子
            eliminateSet.addAll(effectBlockList);

            // 炸弹爆炸
            Set<OperaMaskBlock> quickBoomSet = new HashSet<>();
            for (OperaMaskBlock block : effectBlockList) {
                boolean canBoom = isBomb(block) && !boomList.stream().anyMatch(item -> item.getX() == block.getX() && item.getY() == block.getY());
                if (canBoom) {
                    quickBoomSet.add(block);
                }
            }
            handleWaitBombBoom(listList, eliminateSet, boomList, quickBoomSet);
            waitBoomSet.addAll(quickBoomSet);

            OperaMaskSkillEffect effect = new OperaMaskSkillEffect(eOperaMaskSkillType.Skill04.getValue());
            effect.setBlockList(effectBlockList);
            skillEffectList.add(effect);
        }
    }

    public static void handleSkill07(List<List<OperaMaskBlock>> listList,
                                     List<OperaMaskSkillEffect> effectList) {
        List<OperaMaskBlock> effectBlockList = new ArrayList<>();
        for (List<OperaMaskBlock> list : listList) {
            for (OperaMaskBlock block : list) {
                if (block.getType() == eOperaMaskBlockType.BombColorful.getValue()) {
                    // 替换炸弹需要复制一份出来替换
                    OperaMaskBlock clone = block.clone();
                    clone.setType(eOperaMaskBlockType.BombSuperColorful.getValue());
                    listList.get(clone.getY()).set(clone.getX(), clone);
                    effectBlockList.add(clone);
                }
            }

        }

        if (effectBlockList.size() > 0) {
            OperaMaskSkillEffect effect = new OperaMaskSkillEffect(eOperaMaskSkillType.Skill07.getValue());
            effect.setBlockList(effectBlockList);
            effectList.add(effect);
        }
    }


    public static void handleSkill09(List<List<OperaMaskBlock>> listList,
                                     List<OperaMaskSkill> skillList,
                                     List<OperaMaskSkillEffect> effectList,
                                     int skill09Effect,
                                     Random random,
                                     OperaMaskConfig config,
                                     OperaMaskGameData gameData) {

        List<OperaMaskBlock> effectBlockList = new ArrayList<>();
        for (List<OperaMaskBlock> list : listList) {
            for (OperaMaskBlock block : list) {
                if (block.getColor() == OperaMaskMgr.RED_COLOR && !isBomb(block)) {
                    if (random.nextInt(1000) < skill09Effect) {
                        int bombType = random.nextInt(1000) < 500 ? eOperaMaskBlockType.BombHeng.getValue() : eOperaMaskBlockType.BombShu.getValue();
                        // 替换炸弹需要复制一份出来替换
                        OperaMaskBlock clone = block.clone();
                        clone.setType(bombType);
                        listList.get(clone.getY()).set(clone.getX(), clone);
                        effectBlockList.add(clone);
                    }
                }
            }
        }

        if (effectBlockList.size() > 0) {
            OperaMaskSkillEffect effect = new OperaMaskSkillEffect(eOperaMaskSkillType.Skill09.getValue());
            effect.setBlockList(effectBlockList);
            effectList.add(effect);

            // 横竖炸弹会被替换成十字炸弹
            if (OperaMaskSkillLogic.getSkill03Effect(skillList)) {
                handleSkill03(listList, effectList);
            }
            // 行列变油彩
            List<Integer> skill19Effect = OperaMaskSkillLogic.getSkill19Effect(skillList, config);
            if (skill19Effect != null) {
                handleSkill19(listList, effectList, skill19Effect, gameData);
            }
        }
    }


    private static void handleSkill13(Random random,
                                      List<List<OperaMaskBlock>> listList,
                                      Set<OperaMaskBlock> eliminateSet,
                                      List<OperaMaskBoom> boomList,
                                      Set<OperaMaskBlock> waitBoomSet,
                                      List<OperaMaskSkillEffect> skillEffectList,
                                      int skill13Effect) {
        Set<OperaMaskBlock> lastCheckSet = new HashSet<>(); // 上次检查蔓延的格子
        for (int i = 0; i < 99; i++) { // 假装是while
            Set<OperaMaskBlock> curCheckSet = new HashSet<>(eliminateSet); // 本次检查的格子 = 消除的格子 - 上次检查的格子
            curCheckSet.removeAll(lastCheckSet);
            if (curCheckSet.isEmpty()) {
                break; // 没有了，结束
            }

            Set<OperaMaskBlock> checkSuccessSet = new HashSet<>(); // 本次检查需要消除的格子
            Set<OperaMaskBlock> triggerSet = new HashSet<>(); // 触发蔓延的格子
            for (OperaMaskBlock block : curCheckSet) {
                if (random.nextInt(1000) > skill13Effect) {
                    continue; // 运气差蔓延失败
                }
                int x = block.getX();
                int y = block.getY();
                if (xyExist(listList, x + 1, y)) {
                    OperaMaskBlock tmp = listList.get(y).get(x + 1);
                    if (!eliminateSet.contains(tmp)) {
                        checkSuccessSet.add(tmp);
                        triggerSet.add(block);
                    }
                }
                if (xyExist(listList, x, y + 1)) {
                    OperaMaskBlock tmp = listList.get(y + 1).get(x);
                    if (!eliminateSet.contains(tmp)) {
                        checkSuccessSet.add(tmp);
                        triggerSet.add(block);
                    }
                }
                if (xyExist(listList, x - 1, y)) {
                    OperaMaskBlock tmp = listList.get(y).get(x - 1);
                    if (!eliminateSet.contains(tmp)) {
                        checkSuccessSet.add(tmp);
                        triggerSet.add(block);
                    }
                }
                if (xyExist(listList, x, y - 1)) {
                    OperaMaskBlock tmp = listList.get(y - 1).get(x);
                    if (!eliminateSet.contains(tmp)) {
                        checkSuccessSet.add(tmp);
                        triggerSet.add(block);
                    }
                }
            }

            // 【有效果才处理这些】
            if (checkSuccessSet.size() > 0) {

                // 消除格子
                eliminateSet.addAll(checkSuccessSet);

                // 炸弹爆炸
                Set<OperaMaskBlock> quickBoomSet = new HashSet<>();
                for (OperaMaskBlock block : checkSuccessSet) {
                    boolean canBoom = isBomb(block) && !boomList.stream().anyMatch(item -> item.getX() == block.getX() && item.getY() == block.getY());
                    if (canBoom) {
                        quickBoomSet.add(block);
                    }
                }
                handleWaitBombBoom(listList, eliminateSet, boomList, quickBoomSet);
                waitBoomSet.addAll(quickBoomSet);

                // 首次蔓延需要加触发蔓延的格子
                if (lastCheckSet.isEmpty()) {
                    OperaMaskSkillEffect effect = new OperaMaskSkillEffect(eOperaMaskSkillType.Skill13.getValue());
                    effect.setBlockList(new ArrayList<>(triggerSet));
                    skillEffectList.add(effect);
                }
                // 添加技能效果
                OperaMaskSkillEffect effect = new OperaMaskSkillEffect(eOperaMaskSkillType.Skill1301.getValue());
                effect.setBlockList(new ArrayList<>(checkSuccessSet));
                skillEffectList.add(effect);

            }

            // 添加已检查格子
            lastCheckSet.addAll(curCheckSet);
        }
    }


    private static void handleSkill14(List<List<OperaMaskBlock>> listList,
                                      Set<OperaMaskBlock> eliminateSet,
                                      List<OperaMaskSkillEffect> skillEffectList,
                                      List<OperaMaskBoom> boomList,
                                      Set<OperaMaskBlock> waitBoomSet,
                                      boolean canObliqueEliminate,
                                      int skill14Effect,
                                      Random random,
                                      List<OperaMaskBlock> effect20Blocks,
                                      List<Integer> skill20Effect) {
        // 未被消除的格子
        List<OperaMaskBlock> noEliminateList = new ArrayList<>();
        for (int y = 0; y < listList.size(); y++) {
            if (y < listList.size() - skill14Effect) {
                continue; // 前N行
            }
            for (int x = 0; x < listList.get(y).size(); x++) {
                OperaMaskBlock block = listList.get(y).get(x);
                if (!eliminateSet.contains(block)) {
                    noEliminateList.add(block);
                }
            }
        }
        // 打乱
        Collections.shuffle(noEliminateList);
        // 放进去
        int index = 0;
        OperaMaskSkillEffect effect = new OperaMaskSkillEffect(eOperaMaskSkillType.Skill14.getValue());
        for (int y = 0; y < listList.size(); y++) {
            if (y < listList.size() - skill14Effect) {
                continue; // 前N行
            }
            for (int x = 0; x < listList.get(y).size(); x++) {
                if (!eliminateSet.contains(listList.get(y).get(x))) {
                    OperaMaskBlock block = noEliminateList.get(index++).clone();

                    if ((block.getX() != x || block.getY() != y) && isEffect20(random, skill20Effect)) {
                        effect20Blocks.add(block);
                    }

                    block.setX(x);
                    block.setY(y);
                    listList.get(y).set(x, block);
                    effect.getBlockList().add(block.clone()); // 添加进技能效果
                }
            }
        }
        skillEffectList.add(effect);

        // 消除
        Set<OperaMaskBlock> quickBoomSet = new HashSet<>();
        handleNormalEliminate(listList, eliminateSet, boomList, quickBoomSet, canObliqueEliminate);

        // 快速爆炸
        handleWaitBombBoom(listList, eliminateSet, boomList, quickBoomSet);
        waitBoomSet.addAll(quickBoomSet);
    }

    private static void handleSkill15(List<List<OperaMaskBlock>> listList,
                                      Set<OperaMaskBlock> eliminateSet,
                                      List<OperaMaskSkillEffect> skillEffectList,
                                      List<OperaMaskBoom> boomList,
                                      Set<OperaMaskBlock> waitBoomSet,
                                      Random random,
                                      List<Integer> skill15Effect) {

        int maxMulti = skill15Effect.get(1);
        int ratio = skill15Effect.get(2);

        List<OperaMaskBlock> effectBlockList = new ArrayList<>();
        for (int y = 0; y < listList.size(); y++) {
            for (int x = 0; x < listList.get(y).size(); x++) {
                OperaMaskBlock block = listList.get(0).get(x);
                if (block.getMul15() >= maxMulti) { // 达到最大倍数
                    if (!eliminateSet.contains(block)) { // 未消除
                        if (random.nextInt(1000) < ratio) { // 概率触发
                            effectBlockList.add(block);
                        }
                    }
                }
            }
        }

        if (effectBlockList.size() > 0) {

            // 消除格子
            eliminateSet.addAll(effectBlockList);

            // 炸弹爆炸
            Set<OperaMaskBlock> quickBoomSet = new HashSet<>();
            for (OperaMaskBlock block : effectBlockList) {
                boolean canBoom = isBomb(block) && !boomList.stream().anyMatch(item -> item.getX() == block.getX() && item.getY() == block.getY());
                if (canBoom) {
                    quickBoomSet.add(block);
                }
            }
            handleWaitBombBoom(listList, eliminateSet, boomList, quickBoomSet);
            waitBoomSet.addAll(quickBoomSet);

            OperaMaskSkillEffect effect = new OperaMaskSkillEffect(eOperaMaskSkillType.Skill04.getValue());
            effect.setBlockList(effectBlockList);
            skillEffectList.add(effect);
        }
    }

    public static void handleSkill19(List<List<OperaMaskBlock>> listList,
                                     List<OperaMaskSkillEffect> effectList,
                                     List<Integer> skill19Effect,
                                     OperaMaskGameData gameData) {
        List<OperaMaskBlock> effectBlockList = new ArrayList<>();
        for (List<OperaMaskBlock> list : listList) {
            for (OperaMaskBlock block : list) {
                if (block.getType() == eOperaMaskBlockType.BombHeng.getValue()
                        || block.getType() == eOperaMaskBlockType.BombShu.getValue()
                        || block.getType() == eOperaMaskBlockType.BombShiZi.getValue()) {
                    // 积分倍数
                    int multi = block.getType() == eOperaMaskBlockType.BombShiZi.getValue() ? skill19Effect.get(1) : skill19Effect.get(0);
                    // 替换炸弹需要复制一份出来替换
                    OperaMaskBlock clone = block.clone();
                    clone.setType(eOperaMaskBlockType.NormalBlock.getValue());
                    clone.setMul(clone.getMul() + multi);

                    listList.get(clone.getY()).set(clone.getX(), clone);
                    effectBlockList.add(clone);
                }
            }

        }

        if (effectBlockList.size() > 0) {
            OperaMaskSkillEffect effect = new OperaMaskSkillEffect(eOperaMaskSkillType.Skill19.getValue());
            effect.setBlockList(effectBlockList);
            effectList.add(effect);
        }
    }

    private static void handleSkill20(List<OperaMaskBlock> effect20Blocks,
                                      List<OperaMaskSkillEffect> effectList,
                                      List<Integer> skill20Effect) {

        if (effect20Blocks.isEmpty()) {
            return;
        }

        if (skill20Effect == null || skill20Effect.size() < 2) {
            return;
        }

        OperaMaskSkillEffect effect = new OperaMaskSkillEffect(eOperaMaskSkillType.Skill20.getValue());
        for (OperaMaskBlock block : effect20Blocks) {
            int oldMul20 = block.getMul20();
            int newMul20 = oldMul20 + skill20Effect.get(1);
            block.setMul20(newMul20);

            if (Config.isDebug()) {
                logger.info("----------技能20 触发  -----位置(y,x)=({},{}) 倍数={}", block.getY(), block.getX(), block.getTotalMul());
            }
            effect.getBlockList().add(block.clone()); // 加效果值
        }
        //处理完清空
        effect20Blocks.clear();

        if (!effect.getBlockList().isEmpty()) {
            effectList.add(effect);
        }
    }

    private static boolean isEffect20(Random random, List<Integer> skill20Effect) {
        if (skill20Effect == null || skill20Effect.size() < 2) {
            return false;
        }

        int randomValue = random.nextInt(1000);
        if (randomValue > skill20Effect.get(0)) {
            return false;
        }

        if (Config.isDebug()) {
            logger.info("----------技能20 随机值={} 概率={}-----", randomValue, skill20Effect.get(0));
        }
        return true;
    }

    // 随机3行复制邻行的油彩颜色
    private static void handleSkill21(List<List<OperaMaskBlock>> listList,
                                      Set<OperaMaskBlock> eliminateSet,
                                      List<OperaMaskSkillEffect> skillEffectList,
                                      List<OperaMaskBoom> boomList,
                                      Set<OperaMaskBlock> waitBoomSet,
                                      boolean canObliqueEliminate,
                                      int skill21Effect) {

        //随机N行,并降序
        List<Integer> listRandomY = RandomHelper.getDifferentRandomNum(listList.size(), skill21Effect);
        Collections.sort(listRandomY, (o1, o2) -> o2 - o1);

        //降序取邻行
        OperaMaskSkillEffect effect = new OperaMaskSkillEffect(eOperaMaskSkillType.Skill21.getValue());
        //邻行偏移量
        int lastY = listList.size();
        for (int y : listRandomY) {
            int neighborY = -1;
            if ((y + 1 < listList.size() && y + 1 < lastY) || y == 0) {
                neighborY = y + 1;
            } else if (y - 1 >= 0) {
                neighborY = y - 1;
            } else {
                continue;
            }

            lastY = neighborY < y ? neighborY : y;

            if (Config.isDebug()) {
                logger.info("复制颜色 {} to {} --------", neighborY, y);
            }
            for (int x = 0; x < listList.get(y).size(); x++) {
                OperaMaskBlock block = listList.get(y).get(x);
                if (eliminateSet.contains(block)) {
                    continue;
                }

                if (block.getColor() == 0) {
                    continue;
                }

                OperaMaskBlock neighborBlock = listList.get(neighborY).get(x).clone();

                //没有颜色不会被复制
                if (neighborBlock.getColor() == 0) {
                    continue;
                }
//                if (neighborBlock.getColor() == block.getColor()) {
//                    continue;
//                }
                if (Config.isDebug()) {
                    logger.info("复制颜色 {} to {} ---x={}--- clolor {} to {} ", neighborY, y, x, neighborBlock.getColor(), block.getColor());
                }
                block.setColor(neighborBlock.getColor());
                effect.getBlockList().add(neighborBlock);
                effect.getBlockList().add(block.clone());
            }
        }
        skillEffectList.add(effect);

        // 消除
        Set<OperaMaskBlock> quickBoomSet = new HashSet<>();
        handleNormalEliminate(listList, eliminateSet, boomList, quickBoomSet, canObliqueEliminate);

        // 快速爆炸
        handleWaitBombBoom(listList, eliminateSet, boomList, quickBoomSet);
        waitBoomSet.addAll(quickBoomSet);
    }

    public static void handleSkill22(List<List<OperaMaskBlock>> listList,
                                     List<OperaMaskSkillEffect> effectList,
                                     OperaMaskGameData gameData,
                                     int skill22Effect) {
        List<OperaMaskBlock> effectBlockList = new ArrayList<>();

        int addMul = 0;
        int maxMul = 0;
        OperaMaskBlock maxMulBlock = null;
        for (List<OperaMaskBlock> list : listList) {
            for (OperaMaskBlock block : list) {
                if (block.getTotalMul() <= 1) {
                    continue;
                }
                if (block.getTotalMul() > maxMul) {
                    maxMul = block.getTotalMul();
                    maxMulBlock = block;
                }
                addMul += block.getTotalMul() - 1;
            }
        }

        //吸收不到倍数
        if (maxMulBlock == null || addMul + 1 == maxMul ) {
            return;
        }

        addMul = 0;
        for (List<OperaMaskBlock> list : listList) {
            for (OperaMaskBlock block : list) {
                if (block.getTotalMul() <= 1) {
                    continue;
                }
                //吸收的倍数不包括自身倍数
                if (block == maxMulBlock) {
                    continue;
                }
                addMul += block.getTotalMul() - 1;
                block.ResetTotalMul();
                effectBlockList.add(block.clone());
            }
        }

        //吸收的倍数不包括自身倍数
        if (addMul > 0) {

            int newMul22 = (int) BigDecimal.valueOf(addMul).multiply(BigDecimal.valueOf(skill22Effect)).divide(BigDecimal.valueOf(1000), 0, BigDecimal.ROUND_UP).longValue();

            if (Config.isDebug()) {
                logger.info("技能22 最高倍数(y,x)=({},{})={} 吸收倍数={} 系数={} 吸收后={}  ", maxMulBlock.getY(), maxMulBlock.getX(), maxMul,
                        addMul, skill22Effect, newMul22 + maxMulBlock.getTotalMul());
            }
            newMul22 += maxMulBlock.getTotalMul();
            maxMulBlock.ResetTotalMul();
            maxMulBlock.setMul22(newMul22);
            effectBlockList.add(maxMulBlock.clone());
        }

        if (effectBlockList.size() > 0) {
            OperaMaskSkillEffect effect = new OperaMaskSkillEffect(eOperaMaskSkillType.Skill22.getValue());
            effect.setBlockList(effectBlockList);
            effectList.add(effect);
        }
    }
}
